#include "thex.h"
/* vim:ts=4:sw=4:noet
 * (tabspace=4)
 * 
 * Copyright (C) 2004, 2005 Walter Doekes, <walter@djcvt.net>.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "endian.h"
#include "read.h"
#include "texts.h"
#include "tiger.h"
#ifdef _WIN32
#	define O_LARGEFILE 0
#	define STDIN_FILENO _fileno(stdin)
#else /* !_WIN32 */
#	define _FILE_OFFSET_BITS 64
#	define _LARGEFILE64_SOURCE 1
#	include <unistd.h>
#	define O_BINARY 0
#endif /* !_WIN32 */
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>

int thex_tiger_root(const unsigned char* filename, uint64_t* res,
		int use_mmap, unsigned progress_every_mib) {
	int ret = 0;
	unsigned progress_mib = 0;
	unsigned progress_fs_mib = 0;
	/* Read data */
	const char* p;
	unsigned size = 0;
	/* Read */
	enum { READ_SIZE = 8 * 1024 };
	int fd = -1;
	unsigned char buf[READ_SIZE];
	/* MMAP */
	int last_read_not_1024 = 0;
	struct readmmap_data* rsd;
	unsigned blocksize;
	uint64_t filesize = 0;
	/* Tiger data */
	uint64_t datatmp[3];
#ifdef BIG_ENDIAN
	uint8_t temp[2*3*8];
	int j;
#endif /* BIG_ENDIAN */
	uint64_t data[128]; /* with 128, we have enough room for a 2^137B file */
	unsigned pos = 0;
	uint64_t leafcount = 0;

	/* Open the file stream */
	if(filename == NULL) {
		fd = STDIN_FILENO;
		use_mmap = 0;
	} else if(!use_mmap) {
		struct stat st;
		if((fd = open(filename, O_RDONLY | O_BINARY | O_LARGEFILE)) == -1) {
#ifdef USE_SETERROR2
			set_error("open", -1);
#endif /* USE_SETERROR2 */
			return -1;
		}
		if(fstat(fd, &st) == 0)
			filesize = st.st_size;
	} else {
		if((ret = readmmap_open(filename, &rsd)) != 0)
			return ret;
		/* Check that the blocksize is a multiple of 1024 */
		readmmap_getinfo(&blocksize, &filesize, rsd);
		if(blocksize % 1024 != 0) {
#ifdef USE_SETERROR2
			set_error("thex_tiger_root", THEX_INVALID_BLOCK_SIZE);
#endif /* USE_SETERROR2 */
			return -1;
		}
	}

	progress_fs_mib = filesize / 1048576;

#if 0
	/* 4gb == 2^42, requires 33 blocks */
	if(filesize >= 1024)
		tthblocks = (int)ceil(log((double)filesize / 1024.0) / log(2)) + 1;
	else
		tthblocks = 1;
	printf("fs = %lli, tths = %u\r\n", filesize, tthblocks);
	data = (uint64_t*)malloc(tthblocks * 3 * sizeof(uint64_t));
#endif

	while(1) {
		/* Fetch the next block of data */
		if(!use_mmap) {
			size = 0;
			p = buf;
			while(
				size < READ_SIZE &&
				(ret = (unsigned)read(fd, buf + size, READ_SIZE - size)) > 0
			)
				size += ret;
			if(ret < 0) {
#ifdef USE_SETERROR2
				set_error("read", -1);
#endif /* USE_SETERROR2 */
				break;
			}
			if(size == 0)
				break;
		} else {
			ret = readmmap_get(&p, &size, rsd);
			if(ret <= 0)
				break;
			if(last_read_not_1024) {
				/* this really shouldn't happen, but if it does,
				 * we do not want a corrupt hash.. fix it if a problem emerges */
#ifdef USE_SETERROR2
				set_error("thex_tiger_root", THEX_INVALID_BLOCK_SIZE);
#endif /* USE_SETERROR2 */
				ret = -1;
				break;
			}
		}
		/* Iterate over our fetched block of data */
		while(1) {
			unsigned i;
			tiger_bp(0x00, (const uint64_t*)p, size < 1024 ? size : 1024,
					data + pos);
			++leafcount;
			/* print progress */
			if(progress_every_mib) {
				if(leafcount % 1024 == 1023) {
					++progress_mib;
					if(progress_mib % progress_every_mib == 0) {
						if(progress_fs_mib)
							fprintf(stderr, "(%u/%u)\r", progress_mib,
									progress_fs_mib);
						else
							fprintf(stderr, "(%u/?)\r", progress_mib);
					}
				}
			}
			/* Merge backwards */
			for(i = 2; leafcount % i == 0; i *= 2) {
				pos -= 3;
#ifdef BIG_ENDIAN
				for(j = 0; j < 2*3*8; ++j)
					temp[j ^ 7] = ((uint8_t*)(data + pos))[j];
				tiger_bp(0x01, (const uint64_t*)temp, 6 * sizeof(uint64_t),
						datatmp);
#else /* !BIG_ENDIAN */
				tiger_bp(0x01, (const uint64_t*)data + pos,
						6 * sizeof(uint64_t), datatmp);
#endif /* !BIG_ENDIAN */
				data[pos] = datatmp[0];
				data[pos + 1] = datatmp[1];
				data[pos + 2] = datatmp[2];
			}
			pos += 3;
		
			if(size <= 1024)
				break;

			size -= 1024;
			p += 1024;
		}
		if(use_mmap) {
			if(size != 1024)
				last_read_not_1024 = 1;
		}
	}

	/* Close the stream */
	if(filename == NULL) {
		/* stdin, do nothing */
	} else if(!use_mmap) {
		close(fd);
	} else {
		readmmap_close(&rsd);
	}

	/* Did some error occur? */
	if(ret == -1)
		return ret;
	
	if(pos == 0) {
		/* Nothing written, create the single leaf root */
		tiger_bp(0x00, (const uint64_t*)0, 0, data);
	} else {
		/* Merge backwards */
		pos -= 3;
		while(pos != 0) {
			pos -= 3;
#ifdef BIG_ENDIAN
			for(j = 0; j < 2*3*8; ++j)
				temp[j ^ 7] = ((uint8_t*)(data + pos))[j];
			tiger_bp(0x01, (const uint64_t*)temp, 6 * sizeof(uint64_t),
					datatmp);
#else /* !BIG_ENDIAN */
			tiger_bp(0x01, (const uint64_t*)data + pos, 6 * sizeof(uint64_t),
					datatmp);
#endif /* !BIG_ENDIAN */
			data[pos] = datatmp[0];
			data[pos + 1] = datatmp[1];
			data[pos + 2] = datatmp[2];
		}
	}

	/* Done, return success */
	res[0] = data[0];
	res[1] = data[1];
	res[2] = data[2];
	return 0;
}

#ifdef TEST_THEX_C

#include "base32.h"
#include <stdio.h>

int main(int argc, char** argv) {
	if(argc > 1) {
		uint64_t data[3];
		if(thex_tiger_root(argv[1], data) == 0) {
			unsigned char buf[40];
			uint64tobase32(buf, data, 3);
			printf("%s  %s\r\n", buf, argv[1]);
		} else {
			printf("Something went wrong...\r\n");
		}
	}
	return 0;
}

#endif /* TEST_THEX_C */
